/**
 * dom diff 对比
 * @param {*} parentNode 当前子元素挂载的父元素
 * @param {*} oldChildrenElements 旧的子元素节点
 * @param {*} newChildrenElements 新的子元素节点
 * 
 * 实现原理：
 * 1. 创建老的子元素的节点和key属性之间的映射(key => 值为当前子元素)，用于之后通过映射取得老元素复用
 * 2. 获取新的元素数组（尽可能的复用老的节点，能复用就复用，不能复用就新建）
 *    -- 流程： 从新元素数组的开始从前往后遍历，并且同时在旧的元素数组中查找是否有可复用的元素
 *    能否复用根据key值和type类型判定，如果有可复用的元素，就复用，复用的时候需要判断其位置是否需要
 *    移动(判断方法： 定义一个指向老元素数组中当前能够复用的元素索引，如果之后查找出新的可复用的
 *    元素的索引值小于这个值，就移动位置，同时在每次判断后都需要对这个指针索引进行赋值， 赋值为
 *    当前的老的元素节点的索引值和这个指针索引比较取最大值)
 * 
 */
 function diff(parentNode, oldChildrenElements, newChildrenElements){
	// 创建老元素的key和节点之间的映射
    let oldChildrenElementMap = getChildrenElementMap(oldChildrenElements)
    // 创建新元素的key和节点之间的映射
    let newChildrenElementMap = getNewChildrenElementMap(oldChildrenElementMap, newChildrenElements);
    // 一个指向当前最后一个不需要变动位置的节点的索引指针
    let lastIndex = 0;
    for(let i = 0; i < newChildrenElements.length; i++){
    	// 遍历新的虚拟DOM数组，完成和老元素的对比
        let newChildElement = newChildrenElements[i];
        if(newChildElement){
        	// 如果比对的元素上没有key值就取当前这个元素在子元素数组中的位置索引为key
            let newKey = newChildElement.key || i.toString();
            // 根据新元素的key值在老元素的映射关系中取出老元素（如果有的话）
            let oldChildElement = oldChildrenElementMap[newKey];  
            if(newChildElement == oldChildElement){  // 说明他们是同一个节点，是可复用节点
            	// 判断元素是否需要移动，这里的_mountIndex也是在创建DOM节点的时候挂载上的元素的索引值
                if(oldChildElement._mountIndex < lastIndex){ 
                	// 如果需要变更位置，则将变更操作加入到diffQueue中记录，最后在进行统一的更新
                    diffQueue.push({
                        parentNode,  // 需要移动哪个父节点的下的元素
                        type: MOVE,
                        fromIndex: oldChildElement._mountIndex,  // 初始移动的位置
                        toIndex: i       // 移动到的位置
                    })
                }
                // 重新取得lastIndex的值
                lastIndex = Math.max(oldChildElement._mountIndex, lastIndex);

            }else{ // 如果新老元素不相等，则直接插入元素
                diffQueue.push({
                    parentNode,
                    type: INSERT,
                    toIndex: i,
                    dom: createDOM(newChildElement)
                })
            }
            // 更细挂载的索引，这里直接对新元素操作是因为之前已经将新元素的属性赋值给老元素
            newChildElement._mountIndex = i; 
        }else{
            let newKey = i.toString();
            let oldChildElement = oldChildrenElementMap[newKey];
            // 这里说明newChildrenElement为null,及老的组件将要被卸载，则执行将要卸载的生命周期
            if(oldChildElement.componentInstance && oldChildElement.componentInstance.componentWillUnmount){
                oldChildElement.componentInstance.componentWillUnmount();
            }
        }
    }

    // 遍历老的映射数组，如果在新的数组中没有，则删除调老的数组中元素
    for(let oldKey in oldChildrenElementMap){
        if(!newChildrenElementMap.hasOwnProperty(oldKey)){
            let oldChildElement = oldChildrenElementMap[oldKey];
            diffQueue.push({
                parentNode,
                type: REMOVE,
                fromIndex: oldChildElement._mountIndex,
            })
        }
    }
}
